#pragma once

#include "ast.hpp"

class ExpAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> unary_exp;

		string *Dump(ostream &out) const override {
			return unary_exp->Dump(out);
		}
};

class PrimaryExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeExp = 0,
			TypeLVal,
			TypeNumber
		} type;
		unique_ptr<BaseAST> exp;
		unique_ptr<string> ident;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeExp: return exp->Dump(out);
				case TypeLVal:  {
					if (symtab_manager.Check(*ident)) {
						auto entry = symtab_manager.Get(*ident);
						switch (entry.type) {
							case IdentType::VARI32: {
								auto new_label = new string(to_string(label_manager.Pop()));
								*new_label = "%"+*new_label;
								out << "  " << *new_label << " = load " << entry.name << endl;
								return new_label;
							}
							case IdentType::CONSTI32: {
								return new string(entry.val);
							}
							default: cerr << "Wrong type of " << *ident << endl; exit(-1);
						}
					} else {
						cerr << "Undefined " << *ident << endl;
						exit(-1);
					}
				}
				case TypeNumber: {
					auto my_label = new string(*ident);
					return my_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class UnaryExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypePrimaryExp = 0,
			TypeUnaryExp
		} type;
		char unary_op;
		unique_ptr<BaseAST> exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypePrimaryExp: {
					return exp->Dump(out);
				}
				case TypeUnaryExp: {
					switch(unary_op) {
						case '+': return exp->Dump(out); // Do nothing
						case '-': {
							auto rh = unique_ptr<string>(exp->Dump(out));
							auto my_label = new string("%");
							*my_label += to_string(label_manager.Pop());
							out << "  " << *my_label << " = sub 0, " << *rh << endl;
							return my_label;
						}
						case '!': {
							auto rh = unique_ptr<string>(exp->Dump(out));
							auto my_label = new string("%");
							*my_label += to_string(label_manager.Pop());
							out << "  " << *my_label << " = eq 0, " << *rh << endl;
							return my_label;
						}
						default: assert(false); return nullptr;
					}
				}
				default: assert(false); return nullptr;
			}
		}
};

class MulExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeUnaryExp = 0,
			TypeMul,
			TypeDiv,
			TypeMod
		} type;

		unique_ptr<BaseAST> unary_exp;
		unique_ptr<BaseAST> mul_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeUnaryExp: return unary_exp->Dump(out);
				case TypeMul: {
					auto lf_name = unique_ptr<string>(mul_exp->Dump(out));
					auto rh_name = unique_ptr<string>(unary_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = mul " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeDiv: {
					auto lf_name = unique_ptr<string>(mul_exp->Dump(out));
					auto rh_name = unique_ptr<string>(unary_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = div " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeMod: {
					auto lf_name = unique_ptr<string>(mul_exp->Dump(out));
					auto rh_name = unique_ptr<string>(unary_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = mod " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class AddExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeMulExp = 0,
			TypeAdd,
			TypeSub
		} type;

		unique_ptr<BaseAST> mul_exp;
		unique_ptr<BaseAST> add_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeMulExp: return mul_exp->Dump(out);
				case TypeAdd: {
					auto lf_name = unique_ptr<string>(add_exp->Dump(out));
					auto rh_name = unique_ptr<string>(mul_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = add " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeSub: {
					auto lf_name = unique_ptr<string>(add_exp->Dump(out));
					auto rh_name = unique_ptr<string>(mul_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = sub " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class RelExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeAddExp = 0,
			TypeLT,
			TypeGT,
			TypeLE,
			TypeGE
		} type;

		unique_ptr<BaseAST> add_exp;
		unique_ptr<BaseAST> rel_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeAddExp: return add_exp->Dump(out);
				case TypeLT: {
					auto lf_name = unique_ptr<string>(rel_exp->Dump(out));
					auto rh_name = unique_ptr<string>(add_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = lt " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeGT: {
					auto lf_name = unique_ptr<string>(rel_exp->Dump(out));
					auto rh_name = unique_ptr<string>(add_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = gt " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeLE: {
					auto lf_name = unique_ptr<string>(rel_exp->Dump(out));
					auto rh_name = unique_ptr<string>(add_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = le " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeGE: {
					auto lf_name = unique_ptr<string>(rel_exp->Dump(out));
					auto rh_name = unique_ptr<string>(add_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = ge " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class EqExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeRelExp = 0,
			TypeEQ,
			TypeNE
		} type;

		unique_ptr<BaseAST> rel_exp;
		unique_ptr<BaseAST> eq_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeRelExp: return rel_exp->Dump(out);
				case TypeEQ: {
					auto lf_name = unique_ptr<string>(eq_exp->Dump(out));
					auto rh_name = unique_ptr<string>(rel_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = eq " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				case TypeNE: {
					auto lf_name = unique_ptr<string>(eq_exp->Dump(out));
					auto rh_name = unique_ptr<string>(rel_exp->Dump(out));
					auto my_label = new string("%");
					*my_label += to_string(label_manager.Pop());
					out << "  " << *my_label << " = ne " << *lf_name << ", " << *rh_name << endl;
					return my_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class LAndExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeEqExp = 0,
			TypeLAnd
		} type;

		unique_ptr<BaseAST> eq_exp;
		unique_ptr<BaseAST> land_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeEqExp: return eq_exp->Dump(out);
				case TypeLAnd: {
					auto lf_name = unique_ptr<string>(land_exp->Dump(out));
					auto rh_name = unique_ptr<string>(eq_exp->Dump(out));
					auto first_label = new string("%");
					*first_label += to_string(label_manager.Pop());
					out << "  " << *first_label << " = ne 0, " << *lf_name << endl;
					auto second_label = new string("%");
					*second_label += to_string(label_manager.Pop());
					out << "  " << *second_label << " = ne 0, " << *rh_name << endl;
					auto third_label = new string("%");
					*third_label += to_string(label_manager.Pop());
					out << "  " << *third_label << " = and " << *first_label << ", " << *second_label << endl;
					return third_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class LOrExpAST: public DefaultNullAST {
	public:
		enum Type {
			TypeLAndExp = 0,
			TypeLOr
		} type;

		unique_ptr<BaseAST> land_exp;
		unique_ptr<BaseAST> lor_exp;

		string *Dump(ostream &out) const override {
			switch(type) {
				case TypeLAndExp: return land_exp->Dump(out);
				case TypeLOr: {
					auto lf_name = unique_ptr<string>(land_exp->Dump(out));
					auto rh_name = unique_ptr<string>(lor_exp->Dump(out));
					auto first_label = new string("%");
					*first_label += to_string(label_manager.Pop());
					out << "  " << *first_label << " = ne 0, " << *lf_name << endl;
					auto second_label = new string("%");
					*second_label += to_string(label_manager.Pop());
					out << "  " << *second_label << " = ne 0, " << *rh_name << endl;
					auto third_label = new string("%");
					*third_label += to_string(label_manager.Pop());
					out << "  " << *third_label << " = or " << *first_label << ", " << *second_label << endl;
					return third_label;
				}
				default: assert(false); return nullptr;
			}
		}
};

class ConstExpAST: public DefaultNullAST {
	public:
		unique_ptr<BaseAST> exp;

		string *Dump(ostream &out) const override {
			return exp->Dump(out);
		}
};

